Trò chơi đua xe động vật trong UNITY Engine
- AnimationBuilder.cs
- Unity /
- Editor /
- Spriter2Unity /
- ThirdParty /
- Assets /
- project /
2 Copyright (c) 2014 Andrew Jones, Dario Seyb
3 Based on 'Spriter2Unity' python code by Malhavok
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 THE SOFTWARE.
22 */
23 using System;
24 using System.Collections.Generic;
25 using System.Linq;
26 using System.Text;
27 using UnityEngine;
28 using UnityEditor;
29 using System.IO;
30 using Assets.ThirdParty.Spriter2Unity.Editor.Spriter;
31
32 namespace Assets.ThirdParty.Spriter2Unity.Editor.Unity
33 {
34 using Animation = Spriter.SpriterAnimation;
35 public class AnimationBuilder
36 {
37 private struct SpriteChangeKey
38 {
39 public Sprite Sprite;
40 public float Time;
41 }
42
43 Dictionary<Timeline, GameObject> lastGameObjectCache = new Dictionary<Timeline, GameObject>(); //Used to determine active/inactive toggle
44 Dictionary<Timeline, TimelineKey> lastKeyframeCache = new Dictionary<Timeline, TimelineKey>();
45
46 List<AnimationEvent> animationEvents = new List<AnimationEvent>();
47
48 private string spriteBaseFolder;
49
50 /// <summary>
51 /// Holds a list of sprite change key frames for each sprite object in the hierarchy. Indexed by the relative path to the object.
52 /// </summary>
53 Dictionary<string, List<SpriteChangeKey>> spriteChangeKeys = new Dictionary<string, List<SpriteChangeKey>>();
54
55 AnimationCurveBuilder acb;
56
57 public List<AnimationClip> BuildAnimationClips(GameObject root, Entity entity, string scmlAssetPath)
58 {
59 var allAnimClips = AssetDatabase.LoadAllAssetRepresentationsAtPath(scmlAssetPath).OfType<AnimationClip>().ToList();
60 Debug.Log(string.Format("Found {0} animation clips at {1}", allAnimClips.Count, scmlAssetPath));
61
62 var newAnimClips = new List<AnimationClip>();
63
64 foreach (var animation in entity.Animations)
65 {
66 var animClip = MakeAnimationClip(root, animation, Path.GetDirectoryName(scmlAssetPath));
67 Debug.Log(string.Format("Added animClip({0}) to asset path ({1}) WrapMode:{2}", animClip.name, scmlAssetPath, animClip.wrapMode));
68 newAnimClips.Add(animClip);
69
70 var originalAnimClip = allAnimClips.Where(clip => clip.name == animClip.name).FirstOrDefault();
71 if (originalAnimClip != null)
72 {
73 Debug.Log("Replacing animation clip " + animClip.name);
74 EditorUtility.CopySerialized(animClip, originalAnimClip);
75 allAnimClips.Remove(originalAnimClip);
76 }
77 else
78 AssetDatabase.AddObjectToAsset(animClip, scmlAssetPath);
79 }
80
81 //Remove any animation clips that are no longer present in the SCML
82 foreach(var clip in allAnimClips)
83 {
84 //This may be a bad idea
85 UnityEngine.Object.DestroyImmediate(clip, true);
86 }
87
88 return newAnimClips;
89 }
90
91 public AnimationClip MakeAnimationClip(GameObject root, Animation animation, string baseFolderPath)
92 {
93 //Clear local caches
94 lastGameObjectCache.Clear();
95 animationEvents.Clear();
96 lastKeyframeCache.Clear();
97 spriteChangeKeys.Clear();
98 spriteBaseFolder = baseFolderPath;
99
100 var animClip = new AnimationClip();
101 animClip.name = animation.Name;
102
103 //Set clip to Generic type
104 //AnimationUtility.SetAnimationType(animClip, ModelImporterAnimationType.Generic);
105
106 //Populate the animation curves & events
107 MakeAnimationCurves(root, animClip, animation);
108
109 return animClip;
110 }
111
112 private void MakeAnimationCurves(GameObject root, AnimationClip animClip, Animation animation)
113 {
114 acb = new AnimationCurveBuilder();
115
116 //Get a list of all sprites on this GO
117 var allSprites = root.GetComponentsInChildren<Transform>(true).Select(sr => AnimationUtility.CalculateTransformPath(sr.transform, root.transform));
118
119 //Add a key for all objects on the first frame
120 SetGameObjectForKey(root, animClip, animation.MainlineKeys.First(), 0);
121
122 foreach (var mainlineKey in animation.MainlineKeys)
123 {
124
125 var visibleSprites = SetGameObjectForKey(root, animClip, mainlineKey);
126 var hiddenSprites = allSprites.Except(visibleSprites);
127 HideSprites(root, hiddenSprites, mainlineKey.Time);
128 }
129
130 switch (animation.LoopType)
131 {
132 case LoopType.True:
133 //Cycle back to first frame
134 SetGameObjectForKey(root, animClip, animation.MainlineKeys.First(), animation.Length);
135 break;
136 case LoopType.False:
137 //Duplicate the last key at the end time of the animation
138 SetGameObjectForKey(root, animClip, animation.MainlineKeys.Last(), animation.Length);
139 break;
140 default:
141 Debug.LogWarning("Unsupported loop type: " + animation.LoopType.ToString());
142 break;
143 }
144
145 //Add the curves to our animation clip
146 //NOTE: This MUST be done before modifying the settings, thus the double switch statement
147 acb.AddCurves(animClip);
148
149 foreach (var sprite in spriteChangeKeys)
150 {
151 if (sprite.Value.Count > 0)
152 {
153 BuildSpriteChangeCurve(ref animClip, sprite);
154 }
155 }
156
157 //Set the loop/wrap settings for the animation clip
158 var animSettings = AnimationUtility.GetAnimationClipSettings(animClip);
159 switch(animation.LoopType)
160 {
161 case LoopType.True:
162 animClip.wrapMode = WrapMode.Loop;
163 animSettings.loopTime = true;
164 break;
165 case LoopType.False:
166 animClip.wrapMode = WrapMode.ClampForever;
167 break;
168 case LoopType.PingPong:
169 animClip.wrapMode = WrapMode.PingPong;
170 animSettings.loopTime = true;
171 break;
172 default:
173 Debug.LogWarning("Unsupported loop type: " + animation.LoopType.ToString());
174 break;
175 }
176
177 animClip.SetAnimationSettings(animSettings);
178
179 //Debug.Log(string.Format("Setting animation {0} to {1} loop mode (WrapMode:{2} LoopTime:{3}) ", animClip.name, animation.LoopType, animClip.wrapMode, animSettings.loopTime));
180 }
181
182 private void HideSprites(GameObject root, IEnumerable<string> relativePaths, float time)
183 {
184 foreach(var relativePath in relativePaths)
185 {
186 //Find the gameObject based on relative path
187 var transform = root.transform.Find(relativePath);
188 if (transform == null)
189 {
190 Debug.LogError("ERROR: Unable to find GameObject at relative path " + relativePath);
191 return;
192 }
193
194 var gameObject = transform.gameObject;
195 gameObject.SetActive(false);
196
197 acb.SetCurveActiveOnly(root.transform, transform, time);
198 }
199 }
200
201 private HashSet<string> SetGameObjectForKey(GameObject root, AnimationClip animClip, MainlineKey mainlineKey, float time = -1)
202 {
203 HashSet<string> paths = new HashSet<string>();
204 //Could do this recursively - this is easier
205 Stack<Ref> toProcess = new Stack<Ref>(mainlineKey.GetChildren(null));
206
207 while (toProcess.Count > 0)
208 {
209 var next = toProcess.Pop();
210
211 paths.Add(next.RelativePath);
212 SetGameObjectForRef(root, next, time);
213 SetSpriteEvent(animClip, time, next);
214
215 var children = mainlineKey.GetChildren(next);
216 foreach (var child in children) toProcess.Push(child);
217 }
218
219 return paths;
220 }
221
222 private void SetGameObjectForRef(GameObject root, Ref childRef, float time)
223 {
224 TimelineKey key = childRef.Referenced;
225 if (time < 0) time = key.Time;
226
227 TimelineKey lastKey;
228 lastKeyframeCache.TryGetValue(key.Timeline, out lastKey);
229
230 //Get the relative path based on the current hierarchy
231 var relativePath = childRef.RelativePath;
232
233 //If this is the root, skip it
234 if (string.IsNullOrEmpty(relativePath))
235 {
236 Debug.Log("Skipping root node in SetGameObjectForRef (SHOULD NEVER HAPPEN)");
237 return;
238 }
239
240
241 //Find the gameObject based on relative path
242 var transform = root.transform.Find(relativePath);
243 if (transform == null)
244 {
245 Debug.LogError("ERROR: Unable to find GameObject at relative path " + relativePath);
246 return;
247 }
248
249 var gameObject = transform.gameObject;
250 gameObject.SetActive(true);
251
252 //Get transform data from ref
253 Vector3 localPosition;
254 Vector3 localScale;
255 Vector3 localEulerAngles;
256
257 childRef.BakeTransforms(out localPosition, out localEulerAngles, out localScale);
258
259 //Set the current GameObject's transform data
260 transform.localPosition = localPosition;
261 transform.localScale = localScale;
262
263 //Spin the object in the correct direction
264 var oldEulerAngles = transform.localEulerAngles;
265
266 if (oldEulerAngles.z - localEulerAngles.z > 180) localEulerAngles.z += 360;
267 else if (localEulerAngles.z - oldEulerAngles.z > 180) localEulerAngles.z -= 360;
268 /*
269 switch(childRef.Unmapped.Spin)
270 {
271 case SpinDirection.Clockwise:
272 while (oldEulerAngles.z > localEulerAngles.z) localEulerAngles.z += 360;
273 break;
274 case SpinDirection.CounterClockwise:
275 while (oldEulerAngles.z < localEulerAngles.z) localEulerAngles.z -= 360;
276 break;
277 }*/
278 transform.localEulerAngles = localEulerAngles;
279
280 int zIndex = -1;
281 var spriteKey = key as SpriteTimelineKey;
282 if (spriteKey != null)
283 {
284 zIndex = ((ObjectRef)childRef).ZIndex;
285 //transform.GetComponent<SpriteRenderer>().sortingOrder = zIndex;
286 }
287
288 acb.SetCurve(root.transform, transform, time, lastKey, zIndex);
289
290
291 //Get last-used game object for this Timeline - needed to clean up reparenting
292 GameObject lastGameObject;
293 if (lastGameObjectCache.TryGetValue(key.Timeline, out lastGameObject) && gameObject != lastGameObject)
294 {
295 //Let Unity handle the global->local position cruft for us
296 lastGameObject.transform.position = transform.position;
297 lastGameObject.transform.eulerAngles = transform.eulerAngles;
298
299 //TODO: Also need to do something about scale - this is a little more tricky
300 lastGameObject.transform.localScale = localScale;
301
302 //Deactivate the old object
303 lastGameObject.SetActive(false);
304
305 acb.SetCurve(root.transform, lastGameObject.transform, time, lastKey);
306 }
307
308 //Set cached value for last keyframe
309 lastKeyframeCache[key.Timeline] = key;
310 }
311
312 private void BuildSpriteChangeCurve(ref AnimationClip clip, KeyValuePair<string, List<SpriteChangeKey>> timeline)
313 {
314 // First you need to create Editor Curve Binding
315 EditorCurveBinding curveBinding = new EditorCurveBinding();
316
317 // I want to change the sprites of the sprite renderer, so I put the typeof(SpriteRenderer) as the binding type.
318 curveBinding.type = typeof(SpriteRenderer);
319
320 // Regular path to the GameObject that will be changed
321 curveBinding.path = timeline.Key;
322
323 // This is the property name to change the sprite of a sprite renderer
324 curveBinding.propertyName = "m_Sprite";
325
326 // An array to hold the object keyframes
327 ObjectReferenceKeyframe[] keyFrames = new ObjectReferenceKeyframe[timeline.Value.Count];
328
329 int i = 0;
330 foreach (var key in timeline.Value)
331 {
332 keyFrames[i] = new ObjectReferenceKeyframe();
333 // set the time
334 keyFrames[i].time = key.Time;
335 // set reference for the sprite you want
336 keyFrames[i].value = key.Sprite;
337 i++;
338
339 }
340
341 AnimationUtility.SetObjectReferenceCurve(clip, curveBinding, keyFrames);
342 }
343
344
345 /// <summary>
346 /// Recursively calls SetActive on transform and all children
347 /// </summary>
348 private void SetActiveRecursive(Transform root, bool isActive)
349 {
350 foreach (Transform child in root.transform)
351 {
352 SetActiveRecursive(child, isActive);
353 }
354 root.gameObject.SetActive(isActive);
355 }
356
357 /// <summary>
358 /// Creates an event to change the sprite for the specified Ref (if applicable)
359 /// </summary>
360 /// <param name="clip">Target AnimationClip for Event</param>
361 /// <param name="time">Time at which event should be triggered</param>
362 /// <param name="reference"></param>
363 private void SetSpriteEvent(AnimationClip clip, float time, Ref reference)
364 {
365 var spriteKey = reference.Referenced as SpriteTimelineKey;
366 //Only add event for SpriteTimelineKey objects
367 if (spriteKey != null)
368 {
369 if (time < 0) time = spriteKey.Time;
370 if (!spriteChangeKeys.ContainsKey(reference.RelativePath))
371 {
372 spriteChangeKeys[reference.RelativePath] = new List<SpriteChangeKey>();
373 }
374
375 //Add the key to the dictionary to later build all the curves at once.
376 spriteChangeKeys[reference.RelativePath].Add(new SpriteChangeKey() { Time = time, Sprite = AssetUtils.GetSpriteAtPath(spriteKey.File.Name, spriteBaseFolder) });
377 }
378 }
379 }
380 }
Dictionary
Holds a list of sprite change key frames for each sprite object in the hierarchy. Indexed by the relative path to the object.
Remove any animation clips that are no longer present in the SCML
This may be a bad idea
Clear local caches
Set clip to Generic type
AnimationUtility.SetAnimationType(animClip, ModelImporterAnimationType.Generic);
Populate the animation curves & events
Get a list of all sprites on this GO
Add a key for all objects on the first frame
Cycle back to first frame
Duplicate the last key at the end time of the animation
Add the curves to our animation clip
NOTE: This MUST be done before modifying the settings, thus the double switch statement
Set the loopwrap settings for the animation clip
Debug.Log(string.Format("Setting animation {0} to {1} loop mode (WrapMode:{2} LoopTime:{3}) ", animClip.name, animation.LoopType, animClip.wrapMode, animSettings.loopTime));
Find the gameObject based on relative path
Could do this recursively - this is easier
Get the relative path based on the current hierarchy
If this is the root, skip it
Find the gameObject based on relative path
Get transform data from ref
Set the current GameObject's transform data
Spin the object in the correct direction
transform.GetComponent
Get last-used game object for this Timeline - needed to clean up reparenting
Let Unity handle the global->local position cruft for us
TODO: Also need to do something about scale - this is a little more tricky
Deactivate the old object
Set cached value for last keyframe
First you need to create Editor Curve Binding
I want to change the sprites of the sprite renderer, so I put the typeof(SpriteRenderer) as the binding type.
Regular path to the GameObject that will be changed
This is the property name to change the sprite of a sprite renderer
An array to hold the object keyframes
set the time
set reference for the sprite you want
Recursively calls SetActive on transform and all children
Creates an event to change the sprite for the specified Ref (if applicable)
Target AnimationClip for Event
Time at which event should be triggered
Only add event for SpriteTimelineKey objects
Add the key to the dictionary to later build all the curves at once.